package bl;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

public class IntegersVEBTokensGenerator implements VEBTokensGeneratorI {

	private IntegersPublicParameters ipp;

	public IntegersVEBTokensGenerator(IntegersPublicParameters ipp) {
		this.ipp = ipp;
	}

	@Override
	public PublicParametersI GetPublicParameters() {
		return ipp;
	}

	public BigInteger CreateEBToken(int epoch, FriendClientDataI client)
			throws IneligibleTypeException {
		if (!(client instanceof IntegersFriendClientData)) {
			throw new IneligibleTypeException();
		}

		IntegersFriendClientData ifcd = (IntegersFriendClientData) client;
		BigInteger publicIdentityParam = (BigInteger) ifcd
				.getIdentityPublicParameter();

		// TEST:

		BigInteger b1 = PRF(ifcd.commonSecretKey, epoch, publicIdentityParam);
		BigInteger b2 = PRF(ifcd.commonSecretKey, epoch, publicIdentityParam);

		if (b1.compareTo(b2) != 0)
			System.out.println("ERRRO");
		// //

		return PRF(ifcd.commonSecretKey, epoch, publicIdentityParam);
	}

	@Override
	public VEBTokenI CreateToken(int epoch, FriendClientDataI client)
			throws IneligibleTypeException {
		if (!(client instanceof IntegersFriendClientData)) {
			throw new IneligibleTypeException();
		}

		IntegersFriendClientData ifcd = (IntegersFriendClientData) client;
		BigInteger publicIdentityParam = (BigInteger) ifcd
				.getIdentityPublicParameter();
		// BigInteger secretIdentityParam =
		// (BigInteger)ifcd.getIdentitySecretParameter();
		BigInteger tec = PRF(ifcd.commonSecretKey, epoch, publicIdentityParam);

		// TEST:
		BigInteger b1 = PRF(ifcd.commonSecretKey, epoch, publicIdentityParam);
		BigInteger b2 = PRF(ifcd.commonSecretKey, epoch, publicIdentityParam);

		if (b1.compareTo(b2) != 0)
			System.out.println("ERRRO");
		// //

		return new IntegersVEBToken(
				publicIdentityParam.modPow(tec, this.ipp.G), tec);
	}

	private BigInteger PRF(SecretKey sc, int epoch, BigInteger identity) {
		byte[] output = null;
		try {
			Cipher enc = Cipher.getInstance(IntegersFriendClientData.ENC_ALG);
			enc.init(Cipher.ENCRYPT_MODE, sc, new IvParameterSpec(
					new byte[IntegersFriendClientData.BLOCK_SIZE]));
			byte[] epochBytes = ByteBuffer.allocate(4).putInt(epoch).array();
			byte[] identityBytes = Common.CreatePaddedBigIntegerBytes(identity,
					this.ipp.GetBitLength() / 8);
			byte[] input = Common.concat(identityBytes, epochBytes);

			// Add 0-byte bytes, so the number of bytes is divided by 16:
			int rem = input.length % IntegersFriendClientData.BLOCK_SIZE;
			if (rem > 0) {
				input = Common.concat(input,
						new byte[IntegersFriendClientData.BLOCK_SIZE - rem]);
			}

			output = enc.doFinal(input);
		} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvalidAlgorithmParameterException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return (new BigInteger(output)).mod(this.ipp.G);
	}

	@Override
	public NIZKproofI CreateProof(VEBTokenI vtoken, ClientDataI client)
			throws IneligibleTypeException {
		if (!(vtoken instanceof IntegersVEBToken)
				|| !(client instanceof IntegersClientData)) {
			throw new IneligibleTypeException();
		}

		return new IntegersNIZKproof(this.ipp, (IntegersVEBToken) vtoken,
				((IntegersClientData) client).identitySecretParameter);
	}

	@Override
	public VEBTokenI ParseTokenBytes(byte[] bytes) {
		if (bytes.length != ipp.GetTokenLengthBytes()) {
			throw new InvalidParameterException("Expected to get "
					+ ipp.GetTokenLengthBytes() + " bytes. Received instead "
					+ bytes.length);
		}

		return new IntegersVEBToken(bytes, this.ipp);
	}

	@Override
	public NIZKproofI ParseProofBytes(byte[] bytes) {
		if (bytes.length != ipp.GetProofLengthBytes()) {
			throw new InvalidParameterException("Expected to get "
					+ ipp.GetProofLengthBytes() + " bytes. Received instead "
					+ bytes.length);
		}
		return new IntegersNIZKproof(bytes, ipp);
	}
}
